package com.googlecode.strutson.commands

import com.googlecode.strutson.utils.ConsoleUtils
import com.googlecode.strutson.utils.EnvVariables
import com.googlecode.strutson.utils.ReflectionUtils
import com.googlecode.strutson.utils.TemplateUtils
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.apache.commons.lang.StringUtils
import org.apache.commons.lang.SystemUtils

/**
 * Builds action, views and service for an entity, from a class file 
 */
class Generate {
    String modelName
    String className
    String actionName
    final String suffix
    String templateSuffix
    boolean overwrite
    boolean rest;

    void run() {
        if (rest) {
            templateSuffix = '-rest'
            suffix = 'Controller'
        } else {
            templateSuffix = ''
            suffix = 'Action'
        }
        this.modelName = StringUtils.uncapitalize(readClassName())
        this.className = StringUtils.capitalize(modelName)

        this.actionName = modelName

        def classFile = new File("${EnvVariables.TARGET_MODELS_DIR}/${className}.class")

        def built = false
        if (!classFile.exists()) {
            //class file is not there, try to build the projects
            build()
            built = true
            //check again after build
            if (!classFile.exists()) {
                println "Unable to find [${classFile}]"
                return;
            }
        }
        generateAction()
        generateServiceInterface()
        generateServiceImpl()

        if (!built)
            build()

        if (classFile.exists()) {
            //views
            generateIndex()
            generateNew()
            generateEdit()

            //add entry to menu-items.jsp
            addModelToMenu()
        } else
            println "Unable to find [${classFile}]"

    }

    private def build() {
        //build it
        def maven = SystemUtils.IS_OS_WINDOWS ? 'mvn.bat' : 'mvn'
        println 'Building...'
        Process process = "${maven} -Dmaven.test.skip=true compile".execute()
        process.consumeProcessOutputStream(System.out)
        process.consumeProcessErrorStream(System.out)
        process.waitFor()
    }

    private void addModelToMenu() {
        File menuItems = new File("${EnvVariables.APP_ROOT_DIR}/src/main/webapp/WEB-INF/decorators/menu-items.jsp")
        def fqan = rest ? actionName : "${actionName}/list.action";

        //check if there is a menu item for this model already
        def alreadyInMenu = false
        menuItems.eachLine {
            if (it && it.indexOf(fqan) > 0) {
                alreadyInMenu = true
                return
            }
        }

        if (!alreadyInMenu) {
            def writer = menuItems.newWriter(true);
            if (menuItems.length() == 0) {
                writer.writeLine '<%@ taglib prefix="s" uri="/struts-tags" %>'
            }
            if (rest) {
                writer.writeLine "<span class='menuButton'><a class='list' href='${actionName}'>${className} List</a></span>"
            }
            else {
                writer.writeLine "<s:url var='url' namespace='${actionName}' action='list' />"
                writer.writeLine "<span class='menuButton'><s:a class='list' href='%{#url}'>${className} List</s:a></span>"                
            }
            IOUtils.closeQuietly(writer)
            println "Menu entry added to [${menuItems}]"
        }
    }

    private void generateIndex() {
        def classFile = new File("${EnvVariables.TARGET_MODELS_DIR}/${className}.class")
        def classInfo = ReflectionUtils.getClassInfo(classFile)

        File src = new File("${EnvVariables.HOME}/templates/src/views/list${templateSuffix}.jsp")
        def path = rest ? "${actionName}-index.jsp" : "${modelName}/list-success.jsp";

        File trg = new File("${EnvVariables.VIEWS_DIR}/${path}")
        FileUtils.copyFile(src, trg);

        TemplateUtils.processAndSave(trg, [
                'getters': classInfo.getters,
                'actionName': actionName,
                'modelName': modelName,
                'title': "List of ${className}",
        ])

        println "Generated 'index' view [${trg}]"
    }

    private void generateNew() {
        def classFile = new File("${EnvVariables.TARGET_MODELS_DIR}/${className}.class")
        def classInfo = ReflectionUtils.getClassInfo(classFile)

        File src = new File("${EnvVariables.HOME}/templates/src/views/new${templateSuffix}.jsp")
        def path = rest ? "${actionName}-editNew.jsp" : "${modelName}/new-input.jsp";

        File trg = new File("${EnvVariables.VIEWS_DIR}/${path}")
        FileUtils.copyFile(src, trg);

        TemplateUtils.processAndSave(trg, [
                'setters': classInfo.setters,
                'actionName': actionName,
                'modelName': modelName,
                'title': "New ${className}",
                'constraints': classInfo.constraints
        ])

        println "Generated 'editNew' view [${trg}]"
    }

    private void generateEdit() {
        def classFile = new File("${EnvVariables.TARGET_MODELS_DIR}/${className}.class")
        def classInfo = ReflectionUtils.getClassInfo(classFile)

        File src = new File("${EnvVariables.HOME}/templates/src/views/edit${templateSuffix}.jsp")
        def path = rest ? "${actionName}-edit.jsp" : "${modelName}/edit-input.jsp";

        File trg = new File("${EnvVariables.VIEWS_DIR}/${path}")
        FileUtils.copyFile(src, trg);

        TemplateUtils.processAndSave(trg, [
                'setters': classInfo.setters,
                'actionName': actionName,
                'modelName': modelName,
                'title': "Edit ${className}",
                'constraints': classInfo.constraints
        ])

        println "Generated 'edit' view [${trg}]"
    }

    private void generateAction() {
        File src = new File("${EnvVariables.HOME}/templates/src/actions/simple${templateSuffix}.java")
        File trg = new File("${EnvVariables.ACTIONS_DIR}/${className}${suffix}.java")

        if (!trg.exists() || overwrite) {
            FileUtils.copyFile(src, trg);

            //expand template
            def classFile = new File("${EnvVariables.TARGET_MODELS_DIR}/${className}.class")
            def classInfo = ReflectionUtils.getClassInfo(classFile)
            TemplateUtils.processAndSave(trg, [
                    'modelName': modelName,
                    'actionName': actionName,
                    'actionClassName': "${className}${suffix}",
                    'modelClassName': className,
                    'rootPackage': EnvVariables.ROOT_PACKAGE,
                    'constraints': classInfo.constraints
            ])

            println "Generated action [${trg}]"
        } else {
            println "Skipping existing file [${trg}]"
        }
    }

    private void generateServiceInterface() {
        File src = new File("${EnvVariables.HOME}/templates/src/services/interface.java")
        File trg = new File("${EnvVariables.SERVICES_DIR}/${className}Service.java")
        if (!trg.exists() || overwrite) {
            FileUtils.copyFile(src, trg);

            TemplateUtils.processAndSave(trg, [
                    'modelName': modelName,
                    'actionName': actionName,
                    'actionClassName': "${className}${suffix}",
                    'modelClassName': className,
                    'rootPackage': EnvVariables.ROOT_PACKAGE
            ])
            println "Generated service interface view [${trg}]"
        } else {
            println "Skipping existing file [${trg}]"
        }
    }

    private void generateServiceImpl() {
        File src = new File("${EnvVariables.HOME}/templates/src/services/impl.java")
        File trg = new File("${EnvVariables.SERVICES_IMPL_DIR}/${className}ServiceImpl.java")
        if (!trg.exists() || overwrite) {
            FileUtils.copyFile(src, trg);

            TemplateUtils.processAndSave(trg, [
                    'modelName': modelName,
                    'actionName': actionName,
                    'actionClassName': "${className}${suffix}",
                    'modelClassName': className,
                    'rootPackage': EnvVariables.ROOT_PACKAGE
            ])
            println "Generated service implementation [${trg}]"
        } else {
            println "Skipping existing file [${trg}]"
        }
    }

    String readClassName() {
        def appName = ConsoleUtils.readLine('Enter the model class name: ', {String text ->
            def message = 'Please enter a valid class name'
            boolean valid = true;
            if ((valid = StringUtils.isNotBlank(text))) {
                valid = Character.isJavaIdentifierStart((char) text[0])
                if (valid) {
                    text.substring(1).each {
                        valid = Character.isJavaIdentifierPart((char) it);
                        if (!valid)
                            return;
                    }
                }
            }

            if (!valid) {
                println message
                return false;
            } else {
                return true;
            }
        })

        assert StringUtils.isAlphanumeric(appName);
        return appName;
    }
}